home *** CD-ROM | disk | FTP | other *** search
- Assembly Language for Veggies (And C programmers) Part 2.
-
-
- So, you've wound your way through part one, lashed out and bought the book and
- now you're about to leap into things in a big way, right?
-
- OK... well here is where we make a bit of a start on things! We'll be looking
- first off at a simple routine that displays a number on the screen. sound
- simple? You Wait!
-
- One of the more vital routines one uses from time to time [read all the time!]
- is a simple write number to screen routine. consider what isrequired here.. to
- take a number held wither in a register or memory and display it on the
- screen. simple? you think about the work required to do it and you'll
- start to understand just how hard it is to do...
-
- Firstly, consider the biggest number you wish to write... if it's 8 bits or
- less (0 -255) then you can use one routine, whilst a 16 bit (0-65535) routine
- would be more versatile, BUT if you need to do really big numbers (like free
- space on a hard disk for example) thena 32 bit routine would be called for...
-
- Perhaps the most common of all is the 16 bit routine....
-
- It is coded thus:
-
- WRITE_ASCII: PUSH AX
- PUSH CX
- PUSH DX
- PUSH SI
- MOV AX,DX
- MOV SI,10
- XOR CX,CX
-
- NON_ZERO: XOR DX,DX
- DIV SI
- PUSH DX
- INC CX
- OR AX,AX
- JNE NON_ZERO
-
- WRITE_DIGIT_LOOP: POP DX
- CALL WRITE_HEX_DIGIT
- LOOP WRITE_DIGIT_LOOP
-
- END_DECIMAL: POP SI
- POP DX
- POP CX
- POP AX
- RET
-
- WRITE_HEX_DIGIT: PUSH DX
- CMP DL,10
- JAE HEX_LETTER
- ADD DL,'0'
- JMP SHORT WRITE_DIGIT
-
- HEX_LETTER: ADD DL,'A'-10
-
- WRITE_DIGIT: CALL PRT1
- POP DX
- RET
-
-
- PRT1: PUSH AX
- MOV AH,02
- INT 021
- POP AX
- RET
-
-
- That is one BIG routine..... the Pascal version of which whould be :
-
- program write_num
-
- Var
- number : word;
-
- begin
- number := 5285; {Say}
- write(number);
- end.
-
- We actually have three separate routines here, one called WRITE_ASCII which is
- the one we call, and two more subroutines which write_ascii calls itself. Can
- you name them? They are referanced by CALL instructions... They are
- write_hex_digit and prt1. write_hex_digit calls prt1 so we basicly have a 3
- level nested loop arrangement.
-
- Looking complex yet?
-
- Time to step into the code in more detail... When the routine is first called,
- the number to be displayed should be present in the DX register.
-
- The first job our routine has to do is to convert the number in the register
- to decimal, and then into actuall ASCII digits suitable for writing to the
- screen. Here is where we get tricky... We use a loop and some simple maths to
- derive the decimal equivilant, and the number of loops is equal to the number
- of digits in the final ascii number.
-
- The first instruction you see is the PUSH command. What this does is put the
- contents of the named register upon the stack. In effect, this saves the
- contents in a more or less indestructable area for later recall.. One can
- safely fiddle with the contents of the register, safe in the knowledge that
- it's original contents can be recalled with the use of the POP instruction.
- note that PUSH's and POP's must be done in order... if one does a push AX,
- push bx, then later does a pop ax, pop bx, the contents of ax and bx will be
- exchanged... it's the same as any other stack operation, so keep things in
- order!!
-
- We save the AX, CX, DX snd SI registers.... WHY?? Think about this for a
- second or two.. We save these registers because we modify them in our
- routine! now, why is this important?? Because the main program may too be
- using them!
-
- If you find this hard to understand, follow this analogy... you have a Car
- radio in your car, tuned to your favourite station (MMM-FM 105!) .. you take
- it to the garage for a service. When you get it back you'd expect it to still
- be on MMM wouldn't you? Of course!! Now the average garage jock likes FOX
- better, so he sticks it on fox. If he returned the car to you still on fox,
- you'd be upset and annoyed.... if he was kind enough to return it to MMM
- before giving it back, you'd be none the wizer and go about your way happy as
- larry as the saying goes.
-
- To save our main program from bieng upset (read crashed) by alteration of it's
- registers, our routine saves them on entry and (as you'll soon see) recalls
- them just before exiting. Clear?
-
- OK... now the register-to-decimal routine... involves the loop from
- NON_ZERO: to the JNE instruction BEFORE the WRITE_DIGIT_LOOP: label. before
- entry, some registers are setup... AX is loaded with our initial value. (DX
- stil holds this as well) SI is loaded with 10 decimal and cx is made equal to
- zero (boolean logic dictates that an xor of any number with itself results in
- 0) by the XOR cx,cx instruction.
-
- Firstly DX is zeroed.
- AX is ten divided by SI (10) ... the result is a decimal digit in AX (The
- quotient) and a remainder in AX.
-
- This bit of math goes like this:
-
- Let's say you called the program with 36h in DX. 36h goes into AX, and 10d
- into CX. 36 hex is the same as 54 decimal. 36h(ex) divided by 10d(ecimal)
- equals 5 in AX and 4 in DX.
-
- DX now holds our lest signivigant digit (the 1's unit of you like) - the
- number 4. DX is saved on the stack for use in a second.
-
- CX is incremented from 0 to one. this counts the fact that 1 digit has been
- saved so far.
-
- the or AX,AX basicly checks to see if ax is zero or not. the JNE stands for
- Jump not equal.. this decodes into if the previous math operation (or ax,ax)
- worked out to be equal(ie zero!) then go to the next instruction. if it was
- Not Equal, then go to the label NON_ZERO. it would go to NON_ZERO because AX
- has a 5 in it, and a 0 is needed to not jump!
-
- In the next loop we would then see the remander of 5 in AX still, and again
- the same thing happens...
-
- 6 divided by 10 is a result of 0 (into AX) and a remainder (ie 0.6) into DX.
-
- Again DX is saved on the stack, and CX in incremented. We now have 2 elements
- on the stack, and CX counts them. Ax is now equal to 0, so an or ax,ax proves
- true and the JNE results in a no jump, and the program reaches the
- WRITE_DIGIT_LOOP label for the first time.
-
- At this point we have the hex number stored on the stack in MSB --> LSB format
- (just right for writing to screen!) and a count of the number of digits in CX.
-
- now, here's a trick...
-
- There's some special insttructions built into the 80xxx series that make use
- of the CX register... One of them is the LOOP command.
-
- LOOP does this: Decrements CX by 1. if CX=0, then it goes to the next
- instruction. If CX is not one, however, it jumps to the label (in this case
- WRITE_HEX_DIGIT) coded next to it.
-
- so there's a loop of 3 instructions to be done CX times. what happens is DX is
- popped off the stack, (that's the MSB, the number 5) and the subroutine
- WRITE_HEX_DIGIT is called. in the next loop, the number 4 appears in DX and
- the same routine is called... note that the LOOP command actually decrements
- CX by one, saving us the job of doing it! Quite neat but a trap for young
- programmers... At this point, CX will be equal to 0.
-
- 2 elements have been popped off the stack, so the stack is back at the point
- where we did the PUSH SI..
-
- You may have guessed, write_hex_digit actually does the displaying, and we'll
- look at that in a tic.. but at this point the routine has done it's job and
- it's time to return to the calling program. We restore all the registers we
- saved with the POP commands.. (note how they go in exact reverse order to the
- PUSH's) then go back to our caller with a RET (Short for RETURN [Just like
- BASIC!])
-
- That's all there is to the main loop!
-
- Now, on to the displaying a digit bit...
-
- this should be fairly clear to you.. it's name indicates that it is also
- capable of displaying HEX letters as well... we don't need to worry about
- that however, as all our numbers will be between 0 and 9...
-
- the number comes into the routine in the DX register (I like using DX for
- number passing :-) )
-
- See if you can guess how it works... The numbers 0-9 appear in the ASCII table
- sequentially starting at number 30. The real value of '0' is 30.. got it??
-
- If you want to work out the hex bit, JAE stands for jump if above or equal,
- whilst the ADD dl,'a'-10 must pick the letter, right?
-
- It is important to realize that all this time we've been woring on the 16 bit
- DX register, but we've only been concerned with the bottom 4 bits!! a bit of a
- waste, possibly, but it works and is just as functional as using the dl
- register. I've randomly mixed referance to dl and dx knowing that dl works on
- the BOTTOM (Remember l for lower, h for higher!) 8 bits of dx.
-
- The contents of dl will now be 30+the original number, which is ascii for the
- digit 0-9. All that remains is to actually throw this digit onto the screen.
-
- That's what PRT1 does..
-
- By now, port1 should be self explanitory. Get a pen. Get paper. Scribble down
- how YOU think prt1 works. Take 2 minutes maximum. THEN and only THEN look at
- the next 2 lines... you may use the book I recommended to look up the INT 021
- function (in fact, I insist you do!)
-
- ANSWER: We use AH to indicate which INT 021 function we want, so we load it
- with 02, after saving it's orignial value on the stack so that when we return
- the calling program will have all registers the same. The calling program
- provides the ASCII digit in DL.. the routine expects itthere, so we simply
- call int 021, restor AX and return to the caller. Simple, eh! did you figure
- it out?
-
- you've just learnt some valuable instructions and methods used in ASM. the
- CALL -- RET sequence is the same as BASIC's gosub -- return sequence, the loop
- function repeats CX times, you can temporarily save registers on the stack..
-
- Add anthing else you feel you're getting used to... Quite a bit, isn't it!
- Don't say I didn't warn you!
-
- If you wish to test this routine for yourself (and I suggest you play with it
- for a while) then code this is A86:
-
- BEGIN: mov DX,<stick in a hex value between 00000h and 0ffffh)
- call WRITE_ASCII
- int 020
-
- ; at this point type in all hte code presented above as it appears. pay no
- ; regard to case - the assembler is not case sensitive and I do it for clarity
- ; only!!
-
- WRITE_ASCII:
- .
- .
- .
- .
-
- PRT1:
- .
- .
- .
- .
- RET
-
-
- Assemble this with a random value. Calculate the decimal version (use a
- calculator or something!) and see if it works or not....
-
- Hint: FFFF = 65535 00FF = 256 08C4 = 2244 Honest!!
-
- each time you run it, the number in DX will be printed to the screen.. If you
- feel confidant, fiddle round and see the effect of changing various bits..
- the worst you can do is lock up the machine!
-
- as a challenge, save the CX register somewhere, and display it after the
- number to count the number rof digits in the number!
-
- A hint: Use the prt1 routine tp rint a space character to separate the 2
- numbers, and don't use PUSH to save CX... WHY NOT??
-
-
- Some comments on structured programming
-
-
- You should all know what that is all about....
-
- If not, it basicly says that you divide your chunks of code up into sections
- that only do one, fairly specific job, then call these chunks as you need. Each
- chunk (Well, subroutine or procedure are other names for them, but I'll use
- chunks! [just to be different]) should have one enty point (the bit you CALL)
- and one exit point (IE No JMP's into other chunks, no coding multiple RET's
- into the one chunk) and should not upset the operation of any other chunk (the
- reason for the saving registers on the stack)
-
- Examine the above code.. you see the first chunk converts raw hex to a
- numerical digit. A second chunk is called to do the conversion to ASCII, and a
- third chunk to display it on screen. Each chunk is independant (save the
- actual parameters passed to it and it's output) and can operate from any
- number of calling routines... the WRITE_HEX_DIGIT routine could be called
- directly with a hex digit in DL and would print a HEX digit to the screen.
-
- This is the very essence of modular (Structured) programming. If one does not
- follow this system (in any language, but most importantly in ASM where you
- code tends to become impossible to understand very easily) then things soon
- become a real shit mess that even you cannot follow, much less debug!
-
- Shit mess code that has evolved without thought to structure is often referred
- to as SPAGHETTI CODING - because it's like trying to sort out a bowl of said
- material - near impossible to find the true start and end for all the straggly
- bits!
-
- Librarys of usefull routines are soon built up from this (I use the above
- routine ALL the time in my text based programs) that you know you can drop in
- any program when required and not have to change anything to accomidate it..
- I cannot stress enough how much simpler a set of worked out, debugged, easy to
- call routines makes your programming life!
-
-
- ------------------------------------------------------------------
-
-
- That just about draws together the end of lesson 2 unfortunately... It's
- grown fair bit larger than I'd first hoped, but who cares, I think if I made
- it any smaller, you'd loose track of it...
-
- Next lesson : using A86 and D86 together, more programming examples and a look
- at the INT 021 functions...
-
- In the meantime, read you book from cover to cover. digest as much as you can,
- and what you can't, don't worry about... you should be beginnig to understand
- the register calling convention used with INT 021 - so I suggest you read up
- on that a bit! I'll have a lot to say about MS-DOS and it's problems, quirks,
- hassles etc.... as well as some undoccumented stuff as time goes on...
-
- Until then, good coding! .\\erlin 8/91
-
-